using System;
using System.Xml.Serialization;

namespace SharpMud.MudLib.Core.Time
{
	/// <summary>
	/// Represents a singular moment in time in the *mud* world
	/// </summary>
	[XmlRoot("MudDateTime")]
	public class MudDateTime : IComparable
	{
		internal long ticks;

		#region GetDatePart
		private enum DateTimePart
		{
			Year	= 4,
			Month	= 3,
			Day		= 2,
			Hour	= 1,
			Tick	= 0
		}
		private int GetDatePart(DateTimePart part)
		{
			long tt = ticks;
			
			int years = (int)(tt / MudDateTime.TicksPerYear);
			long tc = (years * MudDateTime.TicksPerYear);
			
			if(part==DateTimePart.Year)
			{
				return years+MudDateTime.Min_Year;
			}
			
			int months = (int)((tt-tc) / MudDateTime.TicksPerMonth);
			tc = (tc + ( months * MudDateTime.TicksPerMonth ) );
			
			if(part==DateTimePart.Month)
			{
				return months+MudDateTime.Min_Month;
			}

            int days = (int)((tt-tc) / MudDateTime.TicksPerDay);
			tc = (tc + ( days * MudDateTime.TicksPerDay ) );

			if(part==DateTimePart.Day)
			{
				return days+MudDateTime.Min_Day;
			}

			int hours = (int)((tt-tc) / MudTimeSpan.TicksPerHour);
			tc = (tc + ( hours * MudTimeSpan.TicksPerHour ) );

			if(part==DateTimePart.Hour)
			{
				return hours+MudDateTime.Min_Hour;
			}

			int rt = (int)(tt-tc);
			tc = tc + rt;

			if(part==DateTimePart.Tick)
			{
				return rt;
			}

			throw new ArgumentOutOfRangeException("part",part,"The DateTimePart was not recognized, so the field cannot be determined.");
		}
		#endregion

		#region Definitions of MudDateTime Bounds
		private const int Min_Year = 1;
		private const int Min_Month = 1;
		private const int Min_Day = 1;
		private const int Min_Hour = 0;
		private const int Min_Tick = 0;
        
		private const int Max_Year = 99999;
		private static int Max_Month {get{return MudDateTime.MonthsPerYear;}}
		private static int Max_Day {get{return MudDateTime.DaysPerMonth;}}
		private static int Max_Hour {get{return MudTimeSpan.HoursPerDay-1;}}
		private static int Max_Tick {get{return (int)MudTimeSpan.TicksPerHour-1;}}
		#endregion

		#region XToTicks
		internal static long DateToTicks(int year, int month, int day)
		{
			int years = year - MudDateTime.Min_Year;
			int months = month - MudDateTime.Min_Month;
			int days = day - MudDateTime.Min_Day;

			long tt =	(years  *	MudDateTime.TicksPerYear)
					+	(months *	MudDateTime.TicksPerMonth)
					+	(days	*	MudDateTime.TicksPerDay);
			return tt;
		}

		internal static long TimeToTicks(int hour, int ticks)
		{
			return MudTimeSpan.TimeToTicks(hour,ticks);
		}
	
		#endregion
		
		#region Instance Math
		public MudDateTime Add(MudTimeSpan ts)
		{
			return this.AddTicks(ts.TotalTicks);
		}
		public MudDateTime Subtract(MudTimeSpan ts)
		{
			return this.Add(ts.Negate());
		}

		public MudTimeSpan Subtract(MudDateTime dt)
		{
			return new MudTimeSpan(this.ticks - dt.ticks);
		}

		public MudDateTime AddDays(double value)
		{
			return this.AddTicks((long)(value * (double)MudTimeSpan.TicksPerDay));
		}
		public MudDateTime AddHours(double value)
		{
			return this.AddTicks((long)(value * (double)MudTimeSpan.TicksPerHour));
		}
		public MudDateTime AddTicks(long value)
		{
			return new MudDateTime(this.ticks + value);
		}
		public MudDateTime AddMonths(int value)
		{
			return this.AddTicks((long)((MudDateTime.DaysPerMonth * value)*MudTimeSpan.TicksPerDay));
		}
		public MudDateTime AddYears(int value)
		{
			return this.AddTicks((long)((MudDateTime.DaysPerYear * value)*MudTimeSpan.TicksPerDay));
		}
		#endregion

		#region Constructors
		public MudDateTime()
		{
			this.ticks = 0;
		}
		public MudDateTime(long t)
		{
			this.ticks = t;
		}
		public MudDateTime(int year, int month, int day, int hour, int tick) 
			: this(MudDateTime.DateToTicks(year,month,day)+MudTimeSpan.TimeToTicks(hour,tick))
		{
		}
		public MudDateTime(int year, int month, int day)
			: this(MudDateTime.DateToTicks(year,month,day))
		{
		}
		#endregion

		#region Static "Constants"
		public static int MonthsPerYear
		{
			get
			{
				return MudTimeConfiguration.Current.MonthsPerYear;
			}
		}
		public static int DaysPerMonth
		{
			get
			{
				return MudTimeConfiguration.Current.DaysPerMonth;
			}
		}
		public static int DaysPerYear
		{
			get
			{
				return MudTimeConfiguration.Current.DaysPerYear;
			}
		}
		public static long TicksPerYear
		{
			get
			{
				return MudDateTime.TicksPerDay*(long)MudDateTime.DaysPerYear;
			}
		}
		public static long TicksPerMonth
		{
			get
			{
				return MudDateTime.TicksPerDay * 
					(long)MudDateTime.DaysPerMonth;
			}
		}
		public static long TicksPerDay
		{
			get
			{
				return MudTimeConfiguration.Current.TicksPerDay;
			}
		}
		#endregion

		#region Portional Properties
		[XmlElement("Year")]
		public int Year
		{
			get
			{
				return GetDatePart(DateTimePart.Year);
			}
			set
			{
				this.ticks = new MudDateTime(value,this.Month,this.Day,this.Hour,this.Tick).ticks;
			}
		}
		[XmlElement("Month")]
		public int Month
		{
			get
			{
				return GetDatePart(DateTimePart.Month);
			}
			set
			{
				this.ticks = new MudDateTime(this.Year,value,this.Day,this.Hour,this.Tick).ticks;
			}
		}
		[XmlElement("Day")]
		public int Day
		{
			get
			{
				return GetDatePart(DateTimePart.Day);
			}
			set
			{
				this.ticks = new MudDateTime(this.Year,this.Month,value,this.Hour,this.Tick).ticks;
			}
		}
		[XmlElement("Hour")]
		public int Hour
		{
			get
			{
				return GetDatePart(DateTimePart.Hour);
			}
			set
			{
				this.ticks = new MudDateTime(this.Year,this.Month,this.Day,value,this.Tick).ticks;
			}
		}
		[XmlElement("Tick")]
		public int Tick
		{
			get
			{
				return GetDatePart(DateTimePart.Tick);
			}
			set
			{
				this.ticks = new MudDateTime(this.Year,this.Month,this.Day,this.Hour,value).ticks;
			}
		}

		public MudTimeSpan TimeOfDay
		{
			get
			{
				return new MudTimeSpan(this.Hour,this.Tick);
			}
		}

		public MudDateTime Date
		{
			get
			{
				return new MudDateTime(this.Year,this.Month,this.Day);
			}
		}
		#endregion

		#region Static Bounding Instances
		public static MudDateTime MinValue
		{
			get
			{
				return new MudDateTime
					(	MudDateTime.Min_Year,MudDateTime.Min_Month,
						MudDateTime.Min_Day,MudDateTime.Min_Hour,MudDateTime.Min_Tick);
			}
		}
		public static MudDateTime MaxValue
		{
			get
			{
				return new MudDateTime
					(	MudDateTime.Max_Year,MudDateTime.Max_Month,
					MudDateTime.Max_Day,MudDateTime.Max_Hour,MudDateTime.Max_Tick);
			}
		}
		#endregion

		#region Static NOW Instance
		public static MudDateTime Now
		{
			get
			{
				return MudTimeSystem.Current.CurrentMudTime;
			}
		}
		#endregion

		#region String Conversions
		public override string ToString()
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			sb.AppendFormat("{0}:{1}",this.Hour,this.Tick);
			sb.Append(" ");
			sb.AppendFormat("Day {0} of {1}, {2}",this.Day.ToString(),((MudLib.Core.Time.MonthOfYear)this.Month).ToString(),this.Year.ToString());
			return sb.ToString();
		}
		public static MudDateTime Parse(string s)
		{
			throw new NotImplementedException();
		}
		public static explicit operator string(MudDateTime dt)
		{
			return dt.ToString();
		}
		public static explicit operator MudDateTime(string s)
		{
			return MudDateTime.Parse(s);
		}
		#endregion

		#region Operator Overloads
		public static MudDateTime operator+(MudDateTime dt, MudTimeSpan ts)
		{
			return new MudDateTime(dt.ticks + ts.TotalTicks);
		}

		public static MudDateTime operator-(MudDateTime dt, MudTimeSpan ts)
		{
			return new MudDateTime(dt.ticks - ts.TotalTicks);
		}

		public static MudTimeSpan operator-(MudDateTime dt1, MudDateTime dt2)
		{
			return new MudTimeSpan(dt1.ticks - dt2.ticks);
		}
		#endregion

		#region Instance Comparing
		public new bool Equals(object obj)
		{
			if(obj.GetType()==typeof(MudDateTime))
			{
				return this.Equals((MudDateTime)obj);
			}
			else
			{	return base.Equals(obj);	}
		}
		public bool Equals(MudDateTime obj)
		{
			return MudDateTime.Equals(this,obj);
		}
		public static bool Equals(MudDateTime dt1, MudDateTime dt2)
		{
			return(dt1.ticks==dt2.ticks);
		}
		#endregion

		#region IComparable Members

		public int CompareTo(object obj)
		{
			if(obj.GetType()==typeof(MudDateTime))
			{
				return CompareTo((MudDateTime)obj);
			}
			else
			{
				throw new InvalidOperationException("Attempt to compare a MudDateTime to an incompatible object.");
			}
		}
		public int CompareTo(MudDateTime obj)
		{
			if(this.ticks>obj.ticks)
				return 1;
			if(this.ticks<obj.ticks)
				return -1;
			return 0;
		}
		#endregion
	}
}
